// jdbcsh - An interactive shell for JDBC databases
// Copyright (C) 2007 Dan Boitnott
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

package jdbcsh;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import jline.ConsoleReader;
import org.apache.commons.lang.ArrayUtils;

/**
 *
 * @author Dan Boitnott
 */
public class Interpreter {
    public class InterpreterException extends Exception {

        public InterpreterException() {
            super();
        }

        public InterpreterException(Throwable err) {
            super(err);
        }

        public InterpreterException(String err) {
            super(err);
        }
    }    
    
    private String buf = "";
    private Dispatcher dsp;
    private ConsoleReader reader;
    private HashMap<String, BackslashCommand> specials = 
            new HashMap<String, BackslashCommand>();

    public Interpreter() {
        initSpecials();
    }

    public Interpreter(Dispatcher disp) {
        this();
        setDispatcher(disp);
    }

    public void setDispatcher(Dispatcher disp) {
        dsp = disp;
    }

    public Dispatcher getDispatcher() {
        return dsp;
    }
    
    public void setReader(ConsoleReader r) {
        reader = r;
    }
    
    public ConsoleReader getReader() {
        return reader;
    }

    public void handleLine(String line) throws InterpreterException {
        String l = line.trim();
        
        // Handle empty lines
        if (l.equals("")) return;

        if (l.charAt(0) == '\\') {
            // Interpreter command
            handleSpecial(l);
        } else if (l.charAt(l.length() - 1) == ';') {
            try {
                // Terminated SQL command
                String sql = buf + l;
                sql = sql.substring(0, sql.length() - 1);
                dsp.sendSQL(sql);
            } catch (Dispatcher.DispatcherException ex) {
                System.err.println("Error: " + ex.getMessage());
            } finally {
                buf = "";
            }
        } else {
            // Unterminated SQL command
            buf += line + "\n";
        }
    }

    private void handleSpecial(String l) {
        String cmd;
        String[] args = {};

        int spidx = l.indexOf(' ');
        if (spidx > 0) {
            cmd = l.substring(1, spidx);
            args = l.substring(spidx).trim().split(" ");
        } else {
            cmd = l.substring(1).trim();
        }
        
        if (!specials.containsKey(cmd)) {
            System.err.println("Invalid command: \\" + cmd);
            return;
        }
        
        try {
            specials.get(cmd).run(args);
        } catch (IOException ex) {
            System.err.println("Fatal IO Exception");
            Main.die(1);
        } catch (UsageException ex) {
            System.err.println("Usage: " + ex.getMessage());
        } catch (Dispatcher.DispatcherException ex) {
            System.err.println("Error: " + ex.getMessage());
            return;
        }

    }
    
    public void printHelp() {
        Iterator it = specials.keySet().iterator();
        while (it.hasNext()) {
            BackslashCommand bc = specials.get(it.next());
            System.err.println("\\" + bc.getCmd() + " " + bc.getUsage() + 
                    " \t" + bc.getHelp());
        }
    }
    
    private void addSpecial(String cmd, String usage, String help, BackslashCommand bc) {
        bc.setCmd(cmd);
        bc.setUsage(usage);
        bc.setHelp(help);
        bc.setInterpreter(this);
        specials.put(cmd, bc);
    }
    
    private void initSpecials() {
        addSpecial("?", "", "list commands", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                printHelp();
            }
        });
        
        addSpecial("d", "object", "describe object", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length != 1) throwUsage();
                dsp.desc(args[0], false);                
            }
        });
        
        addSpecial("d!", "object", "describe object (verbose)", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length != 1) throwUsage();
                dsp.desc(args[0], true);                                                
            }                
        });
        
        addSpecial("d*", "[schema]", "list any type of object", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 0) {
                    dsp.objectSearch(null, false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], false);
                } else throwUsage(); 
            }                
        });
                        
        addSpecial("dt", "[schema]", "list tables", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 0) {
                    dsp.objectSearch(null, "TABLE", false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], "TABLE", false);
                } else throwUsage();
            }                
        });
            
        addSpecial("dv", "[schema]", "list views", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 0) {
                    dsp.objectSearch(null, "VIEW", false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], "VIEW", false);
                } else throwUsage();
            }                
        });
            
        addSpecial("ds", "[schema]", "list system tables", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 0) {
                    dsp.objectSearch(null, "SYSTEM TABLE", false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], "SYSTEM TABLE", false);
                } else throwUsage();
            }                
        });
            
        addSpecial("dm", "[schema]", "list temp tables", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                String[] t = {"GLOBAL TEMPORARY", "LOCAL TEMPORARY"};
                if (args.length == 0) {
                    dsp.objectSearch(null, t, false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], t, false);
                } else throwUsage();
            }                
        });
            
        addSpecial("da", "[schema]", "list aliases", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 0) {
                    dsp.objectSearch(null, "ALIAS", false);
                } else if (args.length == 1) {
                    dsp.objectSearch(args[0], "ALIAS", false);
                } else throwUsage();
            }                
        });
            
        addSpecial("q", "", "quit", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                Main.die();
            }                
        });
            
        addSpecial("c", "url [username] [password]\n\n" +
                        "URL Examples:\n" +
                        "Oracle:      jdbc:oracle:thin:@host:1521:dbsid\n" +
                        "PostgreSQL:  jdbc:postgresql:database\n" +
                        "             jdbc:postgresql://host/database\n" +
                        "             jdbc:postgresql://host:port/database\n", 
                        "connect", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 1) {
                    System.err.println("Connecting...");
                    dsp.connect(args[0]);
                } else if (args.length == 2) {
                    String password = getInterpreter().readPassword();
                    System.err.println("Connecting...");
                    dsp.connect(args[0], args[1], password);
                } else if (args.length == 3) {
                    System.err.println("Connecting...");
                    dsp.connect(args[0], args[1], args[2]);
                } else throwUsage();
            }                
        });
            
        addSpecial("drivers", "", "list drivers", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                dsp.dumpDriverList();
            }                
        });
            
        addSpecial("driver", "DriverClass", "load driver", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length != 1) throwUsage();
                dsp.loadDriver(args[0]);
            }                
        });
            
        addSpecial("loadcsv", "file [table column [column ...]]", "load a CSV file", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length == 1) {
                    dsp.loadCSV(args[0], null, null);
                    return;
                }
                if (args.length < 3) throwUsage();
                String[] cols = (String[]) ArrayUtils.subarray(args, 2, args.length);
                dsp.loadCSV(args[0], args[1], cols);
            }                
        });
            
        addSpecial("dumpcsv", "table file", "dump a table/view to CSV", new BackslashCommand() {
            @Override
            public void run(String[] args) throws UsageException, IOException, Dispatcher.DispatcherException {
                if (args.length != 2) throwUsage();
                dsp.dumpCSV(args[0], args[1]);
            }                
        });
            
    }

    public String getPrompt() {
        if (buf.equals("")) {
            return "% ";
        }

        return "... % ";
    }
    
    public String readPassword() throws IOException {        
        return reader.readLine("Password: ", '.');
    }
}
